Interactor Stack
The web-3d-stdlib Interactor Stack system provides a framework-agnostic way to manage mouse and keyboard events in a structured, tree-like priority system. This system is particularly useful for 3D scene interactions, allowing developers to handle events in a flexible and hierarchical manner.
How It Works
The Interactor Stack system processes events in a top-down approach:
- Event Flow: Events originate from listeners attached to a DOM node and propagate through the interactor stack.
- Tree Structure: The stack forms a tree-like structure where each node can either consume the event or pass it down to its child nodes.
- Dynamic Control: The tree structure can be dynamically re-ordered to suit the needs of the 3D scene, independent of the DOM event listeners.
- Event Augmentation: Events are enriched with additional information, such as the THREE.js state, viewport size, scene details, and methods to refresh the screen.
Example Implementation
Below is an example of how to use the Interactor Stack system with web-3d-stdlib:
Setting Up the Scene
The example initializes a basic THREE.js scene with a cube and integrates the Interactor Stack system:
const interactorStack = new InteractorStackEventSource(
canvas.parentElement!,
() => ({
...threeState,
viewport: {
width: window.innerWidth,
height: window.innerHeight,
top: 0,
left: 0,
},
size: {
width: window.innerWidth,
height: window.innerHeight,
top: 0,
left: 0,
},
})
);
threeState.scene.onBeforeRender = () => interactorStack.onFrame();
interactorStack.register();
Adding Interactors
Interactors are components that handle specific events. For example, the TrackballCameraInteractor is used to enable camera controls:
new TrackballCameraInteractor(interactorStack);
Custom Interactor Example
The ExampleInteractor demonstrates how to create a custom interactor that responds to pointer events:
class ExampleInteractor implements Interactor {
cursor: Mesh;
constructor(public owner: InteractorStackType) {
this.owner.addInteractor(this);
this.cursor = new Mesh(
new SphereGeometry(0.05, 32, 32),
new MeshBasicMaterial({ color: "#ff0000", depthTest: true })
);
}
onPointerMove = (ev: InteractorPointerEvent) => {
const hit = ev.sceneRaycastResults.find(
(hit) => hit.object !== this.cursor
);
if (hit) {
this.cursor.position.copy(hit.point);
if (!this.cursor.parent) {
this.context?.cursorManager.pushCursorState(this, "grab");
ev.three.scene.add(this.cursor);
}
ev.three.invalidate();
} else if (this.cursor.parent) {
this.context?.cursorManager.removeCursorState(this, "grab");
ev.three.scene.remove(this.cursor);
ev.three.invalidate();
}
return InteractorResult.PASS_EVENT;
};
onPointerDown = (ev: InteractorPointerEvent) => {
const m = this.cursor.material as MeshBasicMaterial;
m.color.set("#fff700");
this.context?.cursorManager.pushCursorState(this, "grabbing");
ev.three.invalidate();
return InteractorResult.PASS_EVENT;
};
onPointerUp = (ev: InteractorPointerEvent) => {
const m = this.cursor.material as MeshBasicMaterial;
m.color.set("#ff0000");
this.context?.cursorManager.removeCursorState(this, "grabbing");
ev.three.invalidate();
return InteractorResult.PASS_EVENT;
};
get context(): InteractorStackContext | undefined {
return this.owner.context;
}
}
This interactor creates a red sphere that follows the pointer when it hovers over objects in the scene. It changes color and updates the cursor state on pointer down and up events.
Cleanup
To ensure proper resource management, the interactor stack should be disposed of when no longer needed:
return {
dispose: () => {
interactorStack.dispose();
},
};
The web-3d-stdlib Interactor Stack system provides a powerful and flexible way to manage 3D interactions. By leveraging its tree-like structure and event augmentation, developers can create complex and dynamic interaction systems tailored to their application's needs.